package it.softphone.commons.service.security.jersey;


import it.softphone.commons.service.LogAware;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

import org.apache.commons.lang.NullArgumentException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;

import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.api.model.AbstractResource;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;

/**
 * 
 * @author bsorrentino
 *
 */
public abstract class ShiroResourceFilterFactory implements ResourceFilterFactory, LogAware	/*extends RolesAllowedResourceFilterFactory*/ {

    private @Context SecurityContext sc;
   
	private class AuthzFilter implements ResourceFilter, ContainerRequestFilter {

        private final java.util.List<String> rolesAllowed;
        private final java.util.List<String> permsAllowed;
        private final Logical rolesLogical;
        private final Logical permsLogical;

        public AuthzFilter(java.util.List<String> rolesAllowed, Logical rolesLogical, java.util.List<String> permsAllowed, Logical permsLogical) {
            this.rolesAllowed = rolesAllowed;
            this.rolesLogical = rolesLogical;
            this.permsAllowed = permsAllowed;
            this.permsLogical = permsLogical;
        }

        @Override
        public ContainerRequestFilter getRequestFilter() { return this; }

        @Override
        public ContainerResponseFilter getResponseFilter() { return null; }

        // ContainerRequestFilter
        
        @Override
        public ContainerRequest filter(ContainerRequest request) {
			logger.debug(String.format( "SecurityContext.secure=[%b]", sc.isSecure() ));
        	
			final Subject subject = SecurityUtils.getSubject();
			
			try {
				if( rolesAllowed!=null && !rolesAllowed.isEmpty()) {
					if( rolesLogical.equals(Logical.AND)) {
						subject.checkRoles( rolesAllowed );
					}
					else { // OR
						boolean resOR = false;
						for( String r : rolesAllowed ) {
							
							if( subject.hasRole(r)) {
								resOR = true;
								break;
							}
						}
						if( !resOR )
							throw new AuthorizationException( 
								String.format("user [%s] hasn't any of roles %s", subject.getPrincipal(), rolesAllowed.toArray()));
						
					}
				}
				if( permsAllowed!=null && !permsAllowed.isEmpty()) {
					if( permsLogical.equals(Logical.AND)) {
						subject.checkPermissions( permsAllowed.toArray( new String[permsAllowed.size()]) );
					}
					else { // OR
						boolean resOR = false;
						for( String r : permsAllowed ) {
							
							if( subject.isPermitted(r)) {
								resOR = true;
								break;
							}
						}
						if( !resOR )
							throw new AuthorizationException( 
								String.format("user [%s] hasn't any of permissions %s", subject.getPrincipal(), permsAllowed.toArray()));
						
						
					}
				}
				
				return request;
			}
			catch( AuthorizationException authzEx ) {
				logger.warn("Authz", authzEx);
				throw new WebApplicationException( authzEx, Response.Status.FORBIDDEN );
			}

        }
    }

	private class FailFilter implements ResourceFilter, ContainerRequestFilter {

        private final Response response;
        
        protected FailFilter( String message ) {
        	this.response = Response.status(Response.Status.FORBIDDEN).entity(message).build();
        }

        // ResourceFilter

        @Override
        public ContainerRequestFilter getRequestFilter() {
            return this;
        }

        @Override
        public ContainerResponseFilter getResponseFilter() {
            return null;
        }

        // ContainerRequestFilter
        
        @Override
        public ContainerRequest filter(ContainerRequest request) {            
            throw new WebApplicationException( response );
        }
    }

	/**
	 * 
	 */
    public ShiroResourceFilterFactory() {
		super();
	}

    /**
     * 
     * @return
     */
	public final SecurityContext getSecurityContext() {
		return sc;
	}

	public abstract ResourceFilter[] getAdditionalFilters();
	
	    
	/**
		Below are a list of the different Shiro annotations you can use in your application.
		
		RequiresAuthentication - Requires the current Subject to have been authenticated during their current session for the annotated class/instance/method to be accessed or invoked
		RequiresGuest - Requires the current Subject to be a "guest", that is, they are not authenticated or remembered from a previous session for the annotated class/instance/method to be accessed or invoked.
		RequiresPermissions - Requires the current executor's Subject to imply a particular permission in order to execute the annotated method. If the executor's associated Subject determines that the executor does not imply the specified permission, the method will not be executed.
		RequiresRoles - Requires the currently executing Subject to have all of the specified roles. If they do not have the role(s), the method will not be executed and an AuthorizationException is thrown.
		RequiresUser - Requires the current Subject to be an application user for the annotated class/instance/method to be accessed or invoked.
	 */
	protected final List<ResourceFilter> processAnnotations( List<ResourceFilter> result, AbstractMethod am) {
    	if( result == null ) throw new NullArgumentException("result");
    	if( am == null ) throw new NullArgumentException("am");

		AbstractResource res = am.getResource();
		
		logger.debug( String.format("method=[%s]\nresource=[%s]", am, res ));

		/*
		if( res.isAnnotationPresent( RequiresGuest.class ) || am.isAnnotationPresent( RequiresGuest.class ) ) {
			return result;
		}
		*/
		
		Subject subject = SecurityUtils.getSubject();

		logger.debug( String.format("subject [%s]", subject.getPrincipal()));
		
		/*
		if( (res.isAnnotationPresent( RequiresAuthentication.class ) || am.isAnnotationPresent( RequiresAuthentication.class ) ) 
				&& 
				!subject.isAuthenticated() ) 
		{
			result.add( new FailFilter( "user is not authenticated!") );
			return result;
		}
			
		if( ( res.isAnnotationPresent( RequiresUser.class ) || am.isAnnotationPresent( RequiresUser.class ) ) 
				&& 
				subject.getPrincipal()==null) 
		{
			result.add( new FailFilter( "user is not trusted!") );
			return result;
		}
		*/

		{ // TYPE LEVEL
			java.util.List<String> rolesAllowed = new java.util.ArrayList<String>();
			java.util.List<String> permsAllowed = new java.util.ArrayList<String>();
			Logical rolesLogical = Logical.AND;
			Logical permsLogical = Logical.AND;
			
			if( res.isAnnotationPresent( RequiresRoles.class ) ) {
				RequiresRoles anno = res.getAnnotation(RequiresRoles.class);				
				rolesAllowed.addAll( Arrays.asList(anno.value()));
				rolesLogical = anno.logical();
			}
	
			if( res.isAnnotationPresent( RequiresPermissions.class ) ) {
				RequiresPermissions anno = res.getAnnotation(RequiresPermissions.class);			
				permsAllowed.addAll( Arrays.asList(anno.value()));
				permsLogical = anno.logical();
			}
	
			result.add( new AuthzFilter(rolesAllowed, rolesLogical, permsAllowed, permsLogical ) );

		}

		{ // METHOD LEVEL
			java.util.List<String> rolesAllowed = new java.util.ArrayList<String>();
			java.util.List<String> permsAllowed = new java.util.ArrayList<String>();
			Logical rolesLogical = Logical.AND;
			Logical permsLogical = Logical.AND;
			
			if( am.isAnnotationPresent( RequiresRoles.class ) ) {
				RequiresRoles anno = am.getAnnotation(RequiresRoles.class);			
				rolesAllowed.addAll( Arrays.asList(anno.value()));
				rolesLogical = anno.logical();
			}
	
			if( am.isAnnotationPresent( RequiresPermissions.class ) ) {
				RequiresPermissions anno = am.getAnnotation(RequiresPermissions.class);			
				permsAllowed.addAll( Arrays.asList(anno.value()));
				permsLogical = anno.logical();

			}
			
			result.add( new AuthzFilter(rolesAllowed, rolesLogical, permsAllowed, permsLogical ) );
		}
		return result;
	}
	
	/**
	 * 
	 */
	public final List<ResourceFilter> create(AbstractMethod am) {
		
		final  java.util.List<ResourceFilter> result = new java.util.ArrayList<ResourceFilter>(5);

		processAnnotations(result, am);
		
    	ResourceFilter[] filters = getAdditionalFilters();
    	
    	if( filters != null && filters.length > 0 )  {

    		for( int i = 0 ; i<filters.length; ++i ) result.add( filters[i] );
    		
    	}
	
		return Collections.unmodifiableList(result);
	}

}
